import { Container, Service } from 'typedi';
import { CreateDepartmentDto, DeptPageDto, UpdateDepartmentDto } from '@/dtos';
import { IDepartment, IDepartmentPageResult, IDepartmentWithParentName } from '@/interfaces';
import { PrismaClient, PrismaPromise } from '@/generated/prisma';
import { BadRequestException, DepartmentExistsException, DepartmentNotFoundException } from '@/exceptions';
import { UserRole } from '@utils/formatter';

@Service()
export class DepartmentService {
  public prisma: PrismaClient = Container.get('PRISMA_CLIENT');

  async getAllDepartment(): Promise<IDepartment[]> {
    return this.prisma.department.findMany({
      orderBy: {
        id: 'asc',
      },
    });
  }

  async getAllDepartmentBySelf(departmentId: number, roles: UserRole[]): Promise<IDepartment[]> {
    const isSuperAdmin = !!roles.filter(item => item.code === 'super_admin').length;
    const whereRoot = {
      status: true,
      ...(isSuperAdmin ? {} : { id: departmentId }),
    };
    const roots = await this.prisma.department.findMany({
      where: whereRoot,
      orderBy: { id: 'asc' },
    });
    return await this.appendChildrenDepartments(roots);
  }

  async findAllWithChildren(dto: DeptPageDto): Promise<IDepartmentPageResult> {
    const whereRoot: any = {
      ...(dto.name && { name: { contains: dto.name } }),
      ...(dto.status !== undefined && { status: dto.status }),
      ...(!dto.name && dto.status === undefined && { parentDeptId: null }),
    };
    const total = await this.prisma.department.count({ where: whereRoot });
    const roots = await this.prisma.department.findMany({
      where: whereRoot,
      orderBy: { id: 'asc' },
      skip: dto.offset,
      take: dto.pageSize,
    });
    if (!roots.length) return { list: [], total };
    const list = await this.appendChildrenDepartments(roots);
    return { list, total };
  }

  async findOne(id: number): Promise<IDepartmentWithParentName> {
    const dept = await this.prisma.department.findFirst({
      where: { id },
      include: { parent: { select: { name: true } } },
    });
    if (!dept) throw new DepartmentNotFoundException();
    const { parent, ...rest } = dept;
    return { ...rest, parentDeptName: parent?.name ?? null };
  }

  async create(dto: CreateDepartmentDto): Promise<void> {
    if (await this.existsByName(dto.name)) throw new DepartmentExistsException();
    const parentDeptExists = await this.existsById(dto.parentDeptId);
    if (dto.parentDeptId <= 0 || !parentDeptExists) throw new DepartmentNotFoundException('指定部门不存在');
    await this.prisma.department.create({ data: dto });
  }

  async update(id: number, dto: UpdateDepartmentDto): Promise<void> {
    const dept = await this.prisma.department.findUnique({ where: { id } });
    if (!dept) throw new DepartmentNotFoundException();
    const rootId = await this.getRootId(id);
    const treeNodeIds = await this.getAllChildrenIds([rootId]);
    const nameTaken = await this.prisma.department.findFirst({
      where: { name: dto.name, id: { in: treeNodeIds }, NOT: { id } },
    });
    if (nameTaken) throw new DepartmentExistsException('同一公司部门名称已存在');
    if (dto.parentDeptId) {
      const parent = await this.prisma.department.findUnique({
        where: { id: dto.parentDeptId },
      });
      if (!parent) throw new BadRequestException('指定的父部门不存在');
      const descendantIds = await this.getAllChildrenIds([id]);
      if (descendantIds.includes(dto.parentDeptId)) throw new BadRequestException('不能将当前部门或其子部门设为父级');
    }
    await this.prisma.department.update({
      where: { id },
      data: {
        name: dto.name,
        status: dto.status,
        remark: dto.remark,
        parentDeptId: dto.parentDeptId || null,
      },
    });
  }

  async remove(ids: number[]): Promise<void> {
    await this.prisma.department.deleteMany({ where: { id: { in: ids } } });
  }

  async existsById(id: number): Promise<PrismaPromise<number>> {
    return this.prisma.department.count({ where: { id } });
  }

  async existsByName(name: string): Promise<PrismaPromise<number>> {
    return this.prisma.department.count({ where: { name } });
  }

  /**
   * 递归查询所有子孙部门 id
   * @param parentIds 父 id 数组
   * @param acc 累加器，默认传 []
   */
  private async getAllChildrenIds(parentIds: number[], acc: number[] = []): Promise<number[]> {
    if (parentIds.length === 0) return acc;
    const children = await this.prisma.department.findMany({
      where: { parentDeptId: { in: parentIds } },
      select: { id: true },
    });
    const childIds = children.map(c => c.id);
    acc.push(...parentIds);
    return this.getAllChildrenIds(childIds, acc);
  }

  private async getRootId(deptId: number): Promise<number> {
    let cur = await this.prisma.department.findUnique({
      where: { id: deptId },
      select: { id: true, parentDeptId: true },
    });
    if (!cur) throw new DepartmentNotFoundException();
    while (cur.parentDeptId !== null) {
      cur = await this.prisma.department.findUnique({
        where: { id: cur.parentDeptId },
        select: { id: true, parentDeptId: true },
      });
      if (!cur) break;
    }
    return cur.id;
  }

  private async appendChildrenDepartments(roots: IDepartment[]): Promise<IDepartment[]> {
    if (!roots.length) return [];
    const seen = new Set<string>();
    const result: IDepartment[] = [];
    const queue = [...roots];
    for (const root of roots) seen.add(String(root.id));
    while (queue.length) {
      const current = queue.shift();
      if (!current) continue;
      result.push(current);
      const children = await this.prisma.department.findMany({
        where: { parentDeptId: current.id },
      });
      for (const child of children) {
        if (!seen.has(String(child.id))) {
          seen.add(String(child.id));
          queue.push(child);
        }
      }
    }
    return result;
  }
}
